#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h> // copy to user
#include <asm/io.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include "git_version.h"


#define DRIVER_NAME "kmem"


static char *version = GIT_VERSION;
module_param(version, charp, S_IRUGO);


#define KMEMBUF_SIZE    (10 * 1024 * 1024)

struct kmem_buf {
    void *ptr[KMEMBUF_SIZE];
    int index;
};

struct kmem_buf *g_kmem = NULL;

static void *kmem_alloc(int size)
{
    void *ptr;

    ptr = kzalloc(size, GFP_KERNEL | GFP_DMA32);
    if (ptr == NULL)
        pr_info("kmem alloc err!\n");

    return ptr;
}

static void kmem_free(void *ptr)
{
    kfree(ptr);
}

static int kmem_open(struct inode *inode, struct file *file)
{
    struct device *dev = NULL;
    struct miscdevice *misc_dev = file->private_data;
    dev = misc_dev->this_device;

    g_kmem = vzalloc(sizeof(struct kmem_buf));
    if (g_kmem == NULL) {
        pr_info(DRIVER_NAME " open failed.\n");
        return -ENOMEM;
    }

    pr_info(DRIVER_NAME " open.\n");
    return 0;
}

static int kmem_release(struct inode *inode, struct file *file)
{
    int i;

    for (i = 0; i < g_kmem->index; i++) {
        kmem_free(g_kmem->ptr[i]);
    }

    vfree(g_kmem);
    g_kmem = NULL;

    pr_info(DRIVER_NAME " release. free count: %d\n", i);

    return 0;
}

static long kmem_ioctl(struct file *filp, unsigned int cmd, unsigned long size)
{
    void *ptr;

    switch (cmd) {
        case 1: 
            ptr = kmem_alloc(size);
            if (ptr == NULL)
                return -ENOMEM;
            g_kmem->ptr[g_kmem->index++] = ptr;
            break;
        case 2:
            ptr = g_kmem->ptr[--g_kmem->index];
            kmem_free(ptr);
            break;
        default:
            return -1;
    }

    return 0;
}

static const struct file_operations kmem_fops = {
    .owner = THIS_MODULE,
    .open = kmem_open,
    .release = kmem_release,
    .unlocked_ioctl = kmem_ioctl,
};

static struct miscdevice kmem_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DRIVER_NAME,
    .fops = &kmem_fops,
};

static int __init kmem_init(void)
{
    int ret;

    ret = misc_register(&kmem_device);
    if (ret < 0) {
        pr_err("Failed to register misc device\n");
        return ret;
    }

    pr_info("MISC device registered\n");

    return 0;
}

static void __exit kmem_exit(void)
{
    misc_deregister(&kmem_device);
    pr_info("MISC device unregistered\n");
}


module_init(kmem_init);
module_exit(kmem_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple MISC driver example");
